#!/usr/bin/perl
#
# Take Objective-C Methods and create NSLog()s of them ('cause I'm lazy)
#
#  Author: Jim Turner <myztikjenz@gmail.com>
# Version: 1.1
# License: MIT  http://www.opensource.org/licenses/mit-license.php
#      
#      Copyright (c) 2008-2010 Jim Turner
#      Permission is hereby granted, free of charge, to any person obtaining a copy
#      of this software and associated documentation files (the "Software"), to deal
#      in the Software without restriction, including without limitation the rights
#      to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#      copies of the Software, and to permit persons to whom the Software is
#      furnished to do so, subject to the following conditions:
#
#      The above copyright notice and this permission notice shall be included in
#      all copies or substantial portions of the Software.
#
#      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#      IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#      FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#      AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#      LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#      OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#      THE SOFTWARE.
#
# Known Issues:
#  - Arguments with multiple pointers/function pointers aren't supported yet 
#      (e.g., - (void)sortSubviewsUsingFunction:(NSComparisonResult (*)(id, id, void *))compare context:(void *)context
#
#  - Some C types aren't handled properly (e.g., (const char **))
#
#  - typedefs of primitives are handled somewhat kludgy (the %maps under options).  While I can
#    dynamically find types, I'm not sure it's worth it at this point in time.
#
# Revision History:
# v1.1 - 2009.11.16 - Calls to super that return a value are now part of the log statement
# v1.0 - 2008.09.06 - Initial revision
#
##################################################################
use strict;

##################################################################
# OPTIONS
#   NOTE: I'd make these dynamic based off filename or switch or something if it were possible.
#         But Xcode copies the file (to some guid-lookin' thing) and passing switches isn't supported)
#
# - Instead of logging a description, log the address instead
our $logAddressOfObjects = 0;

# - Insert a call to super
our $insertCallToSuper = 0;

# - Log the return call from super. Will implicitly turn on insertCallToSuper if needed
our $logReturnFromSuper = 0;

# - When logging the result from super, what variable name should we use?
our $superResponseVarName = "__super";

# - Log 'self' as the first item in the output (e.g., NSLog( @"%@ dealloc", self ); )
#   Will follow $logAddressOfObjects on whether it prints just an address or description.
our $logSelf = 1;

# - Converting types into primitives
our %uIntegerMap = map{ $_ => 'NSUInteger' } qw( NSTIFFCompression );
our %integerMap  = map{ $_ => 'NSInteger' }  qw( NSWindowOrderingMode NSDragOperation );
our %doubleMap   = map{ $_ => 'double' }     qw( NSTimeInterval );

#
##################################################################
my $line = <<SELECTION;
%%%{PBXSelectedText}%%%
SELECTION

my $originalHadNewline = ($line =~ m/\012\012/) ? 1 : 0;
chomp( $line );
$line =~ s/\s+$//g;

my $originalHadInlineBracket = ($line =~ m/\{/) ? 1 : 0;

# Strip and save the return type then store for a call to super
$line =~ s/^\s*(\+|\-)\s*\((\s*[^\)]+)\)\s*(.+)$/$3/;

my $originalReturnValue = $2;

my $superCallRequiresReturn = 1;
$superCallRequiresReturn = 0 if( $originalReturnValue eq "void" or $originalReturnValue eq "IBAction" );

my $superCall = $line;

#print "line at: $line\n";

my @methodNames = ();
my @argTypes    = ();
my @arguments   = ();

while( $line =~ m/([^\:]+):\s*\(\s*([^\)]+)\s*\)\s*([^\s]+)\s*/g )
{
#	print "$1|$2|$3\n";

	push( @methodNames, $1 );
	push( @argTypes, $2 );
	push( @arguments, $3 );
}

my $outputString = 'NSLog( @"';
my $argumentString = "";

if( $#methodNames == -1 ) # didn't find any method:(type)arg patterns.  Probably an unarg'd method
{
	$line =~ s/^([^\W]+).*$/$1/;

	_logSelf() if( $logSelf );

	$outputString .= $line;
}
else
{
	_logSelf() if( $logSelf );

	for( my $x=0; $x<=$#methodNames; $x++ )
	{
		my $mName = $methodNames[$x];
		my $aType = $argTypes[$x];
		my $arg   = $arguments[$x];
	
		chomp( $aType );
		
		$outputString .= $mName;
		
		if( $aType ne "" )
		{
			$outputString .= ':';
			my( $placeholder, $argString ) = _placeholderAndArgStringForType( $aType, $arg );

			$outputString .= "$placeholder ";
			if( $argString ne "" )
			{
				$argumentString .= ", $argString";
			}
		}
	}
}

# Output the result
my $callToSuper;
my $returnFromSuper;

print "\n" if( !$originalHadNewline );
print "{\n" if( !$originalHadInlineBracket );

if( $insertCallToSuper or ($logReturnFromSuper and $superCallRequiresReturn) )
{
	# Remove everything from the last arg on
	my $lastArg = $arguments[$#arguments];
	if( $lastArg ne "" )
	{
		$superCall =~ s/($lastArg)[^$lastArg]+$/$1/;
		$superCall =~ s/\([^\)]+\)//g;
	}
	else
	{
		$superCall =~ s/^([^\W]+).*$/$1/;
	}
	
	$callToSuper = "[super $superCall]";
	
	if( $superCallRequiresReturn )
	{
		if( $logReturnFromSuper )
		{
			$returnFromSuper = "return( $superResponseVarName )";
		}
		else
		{
			$returnFromSuper = "return( $callToSuper )";
		}
	}
}

if( $logReturnFromSuper and $superCallRequiresReturn )
{
	my( $placeholder, $argString ) = _placeholderAndArgStringForType( $originalReturnValue, $superResponseVarName );
	print "\t$originalReturnValue $superResponseVarName = $callToSuper;\n";

	if( $argString ne "" )
	{
		$outputString .= "[super replied: $placeholder]\"" . $argumentString . ", " . $argString . ' );';
	}
	else
	{
		$outputString .= "[super replied: $placeholder]\"" . $argumentString . ' );';
	}
}
else
{
	$outputString .= '"' . $argumentString . ' );';
}
	
print "\t$outputString\n";

if( $returnFromSuper )
{
	print "\t$returnFromSuper;\n";	
}
elsif( $callToSuper )
{
	print "\t$callToSuper;\n";
}


print "}\n";

##################################################################

sub _placeholderAndArgStringForType
{
	my( $type, $arg ) = @_;
	my $placeholder = "";
	my $argString   = "";
	
	$type = _convertType( $type );
	
	if( $type ne "" )
	{
		if( $type =~ /void\s*\*/ ) # void pointer
		{
			$placeholder = '(void*)<%p>';
			$argString = "$arg";
		}
		elsif( $type =~ /oneway\s+void/ )
		{
			$placeholder = '(oneway void)';
		}
		elsif( $type =~ /id\s*\<[^\>]+\>/ ) # id <Protocol>
		{
			$placeholder = '%@';
			$argString = "$arg";
		}
		elsif( $type =~ /\*\*/ ) # pointer address
		{
			$placeholder = '%@';
			$argString = "(*$arg) ? [NSString stringWithFormat:\@\"<\%p>\",*$arg] : \@\"nil\"";
		}
		elsif( $type =~ /\*/ ) # pointer of some type
		{		
			$placeholder = ( $logAddressOfObjects ) ? '<%p>' : '%@';
			$argString = "$arg";
		}
		elsif( $type eq "id" )
		{		
			$placeholder = ( $logAddressOfObjects ) ? '<%p>' : '%@';
			$argString = "$arg";
		}
		elsif( $type eq "SEL" )
		{		
			$placeholder = '%@';
			$argString = "NSStringFromSelector($arg)";
		}
		elsif( $type eq "Class" )
		{		
			$placeholder = '%@';
			$argString = "NSStringFromClass($arg)";
		}
		elsif( $type eq "NSRect" or $type =~ /struct NSRect/ )
		{		
			$placeholder = '%@';
			$argString = "NSStringFromRect($arg)";
		}
		elsif( $type eq "CGRect" or $type =~ /struct CGRect/ )
		{		
			$placeholder = '%@';
			$argString = "NSStringFromRect(NSRectFromCGRect($arg))";
		}
		elsif( $type eq "NSPoint" or $type =~ /struct NSPoint/ )
		{		
			$placeholder = '%@';
			$argString = "NSStringFromPoint($arg)";
		}
		elsif( $type eq "CGPoint" or $type =~ /struct CGPoint/ )
		{		
			$placeholder = '%@';
			$argString = "NSStringFromPoint(NSPointFromCGPoint($arg))";
		}
		elsif( $type eq "NSSize" or $type =~ /struct NSSize/ )
		{		
			$placeholder = '%@';
			$argString = "NSStringFromSize($arg)";
		}
		elsif( $type eq "CGSize" or $type =~ /struct CGSize/ )
		{		
			$placeholder = '%@';
			$argString = "NSStringFromSize(NSSizeFromGCSize($arg))";
		}
		elsif( $type eq "BOOL" )
		{
			$placeholder = '%@';
			$argString = "($arg) ? \@\"YES\" : \@\"NO\"";
		}
		elsif( $type =~ /NSInteger|int/i )
		{
			$placeholder = '%d';
			$argString = "$arg";
		}
		elsif( $type =~ /NSUInteger|uint|unsigned\s+int/i )
		{
			$placeholder = '%u';
			$argString = "$arg";
		}
		elsif( $type =~ /float|double/i )
		{
			$placeholder = '%f';
			$argString = "$arg";
		}
		elsif( $type =~ /long\s+long/ )
		{
			$placeholder = '%qi';
			$argString = "$arg";
		}
		elsif( $type =~ /unsigned long\s+long/ )
		{
			$placeholder = '%qu';
			$argString = "$arg";
		}
		elsif( $type =~ /short/ )
		{
			$placeholder = '%hi';
			$argString = "$arg";
		}
		elsif( $type =~ /unsigned short/ )
		{
			$placeholder = '%hu';
			$argString = "$arg";
		}
		else
		{
			# Unknown type
			$placeholder = "??($type)";
		}
	}
	
	return( ($placeholder, $argString) );
}

sub _convertType
{
	my $type = shift;
	
	return( $uIntegerMap{$type} ) if( $uIntegerMap{$type} );
	return( $integerMap{$type} ) if( $integerMap{$type} );
	return( $doubleMap{$type} ) if( $doubleMap{$type} );
	
	return( $type );
}

sub _logSelf
{
	if( $logAddressOfObjects )
	{
		$outputString .=  '<%@ %p> ';
		$argumentString .= ', [self class], self';
	}
	else
	{
		$outputString .= '%@ ';
		$argumentString .= ', self';
	}
}

##################################################################
#
# Dynamic type discovery... hmm...
# Types are defined in runtime.h
# gcc -o ultraTinyType -ObjC -framework Foundation ultraTinyType.m
__DATA__
#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>

int main( int argc, char *argv[] )
{
	printf( "%s", @encode( [TYPE] ) );
	return( 0 );
}